#!/usr/bin/env ruby

##
# Copyright (C) 2014 Hai Bison
#
# See the file LICENSE at the root directory of this project for copying
# permission.
#

require 'optparse'
require 'ostruct'

begin
    require 'unidecoder'
rescue Exception
    puts " ! This script requires Ruby gem `unidecoder`. Please install it first."
    puts "   You can use this command:"
    puts
    puts "       $ gem install unidecoder"
    puts

    exit 1
end

##
# This module contains version information.
#
module Kiem
    ##
    # The name of this script. It means "search" (verb) in Vietnamese  :-)
    #
    SCRIPT_NAME = "Kiếm"

    ##
    # Version code.
    #
    VERSION_CODE = 6

    ##
    # Version name, this follows [semantic versioning](www.semver.org).
    #
    VERSION_NAME = '0.0.6'

    ##
    # Homepage.
    #
    HOMEPAGE = 'http://www.haibison.com'

    ##
    # Contact.
    #
    CONTACT_EMAIL = 'haibison.apps@gmail.com'
end # Kiem

##
# Helper module for formatting text.
#
module Colours

    ##
    # Map of colours.
    #
    # References: [http://misc.flogisoft.com/bash/tip_colors_and_formatting]
    #
    COLOURS = { :black => '[30m', :blue => '[34m', :dark_gray => '[90m',
                :default => '[39m', :green => '[32m', :magenta => '[35m',
                :cyan => '[36m', :light_blue => '[94m', :light_cyan => '[96m',
                :light_gray => '[37m', :light_green => '[92m',
                :light_magenta => '[95m', :light_red => '[91m',
                :light_yellow => '[93m', :red => '[31m', :white => '[97m',
                :yellow => '[33m' }

    ##
    # Reset formatting.
    #
    RESET = "\e[0m"

    ##
    # Formats given text as bold.
    #
    def self.bold s
        "\e[1m#{s}#{RESET}" %s
    end # bold

    ##
    # Formats given text with given colour.
    #
    def self.colour s, c
        "\e#{COLOURS[c]}#{s}#{RESET}"
    end # colour

end # Colours

DESCRIPTION = "
        *** Kiếm ***

    - Version : #{Kiem::VERSION_NAME} (revision: #{Kiem::VERSION_CODE})
    - Homepage: #{Kiem::HOMEPAGE}
    - Contact : #{Kiem::CONTACT_EMAIL}

    Usage: you pipe content to this script, followed by your search text.

    For example, to search for files which their names *contain* `café`:

        $ find | #{File.basename $0} cafe

    Notes:

    - You don't need to type the accent.  Even if you do, the script still
      strips out all accents.

    - Current version only supports raw text. Neither glob or regular expression
      is supported.

    - Currently we don't know if this script works for languages which don't use
      Latin characters, such as Chinese, Korean, Japanese, Arabic...  If one of
      them is your mother language, please let us know.  We thank you  :-)
"

##
# Parses arguments.
#
def parse_args args=ARGV.clone
    result = OpenStruct.new

    OptionParser.new do |opts|
        opts.banner = DESCRIPTION
        opts.separator ''
        opts.separator 'Optional parameters:'

        opts.on('-i', '--ignore-case', "ignore case") do
            result.ignore_case = true
        end

        opts.on('-r', '--read',
                "consider each line of input as a file name, and try to read it.",
                "If the line is not a file name, just handle it as a text.") do
            result.read = true
        end

        opts.on('-v', '--verbose', "print verbose messages") do
            result.verbose = true
        end

        opts.on('-d', '--debug', "print debug messages") do
            result.debug = true
        end

        opts.on('-h', '--help', "print help") do
            puts opts
            exit
        end
    end.parse! args

    result.pattern = args.shift
    result.pattern = result.pattern.downcase if result.ignore_case

    args.clear

    result
end # parse_args

##
# Line parser.
#
class LineParser

    ##
    # Creates new instance.
    #
    # # Parameters
    #
    # * `options`: the options.
    #
    def initialize options
        @options = options
    end # initialize

    ##
    # Parses given line.
    #
    # # Parameters
    #
    # * `line`: the line to be parsed.
    # * `print`: set to `true` to print out `line` if parsed OK.
    #
    # # Returns
    #
    # The given line if found. If gem `colorize` is available, returned string
    # will be formatted as well.
    #
    # Returns nil otherwise.
    #
    def parse line, print=false
        s = line.to_ascii
        s.downcase! if @options.ignore_case
        if index = s.index(@options.pattern)
            line = line.clone
            sub_str = line.slice! line[index..index + @options.pattern.size - 1]
            line.insert index, Colours.bold(Colours.colour(sub_str, :yellow))

            puts line if print
            line
        end
    end # parse

end # LineParser

if __FILE__ == $0
    options = parse_args ARGV
    line_parser = LineParser.new options

    begin
        ARGF.each_line do |line|
            line.strip!
            puts " > Processing: '#{line}'" if options.debug

            if options.read and File.file? line
                found = nil
                File.open(line, 'r').each_line do |line2|
                    if line2.valid_encoding?
                        if line2 = line_parser.parse(line2)
                            if ! found
                                found = true

                                msg = " > File: #{File.absolute_path line}"
                                msg = Colours.bold Colours.colour msg, :green
                                puts msg
                            end
                            puts line2
                        end # if parsed
                    else
                        puts "\t...is a binary file, ignored" if options.debug
                        break
                    end
                end # for each line in file
            else
                line_parser.parse line, true
            end
        end # for each line
    rescue Interrupt
        puts "\n > #{Colours.bold 'Stopped on demand'}"
    end
end # if __FILE__ == $0
